package com.serviceinfo.impl;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.serviceinfo.model.ParameterInfo;
import com.serviceinfo.model.ServiceInfo;

public class ServiceInfoImpl implements ServiceInfo {

	private final String serviceName;

	private final String serviceUrl;

	private final String serviceMethod;

	private final Type resultType;

	private final List<ParameterInfo> pathParams = new ArrayList<ParameterInfo>();

	private final List<ParameterInfo> queryParams = new ArrayList<ParameterInfo>();

	private final List<ParameterInfo> formParams = new ArrayList<ParameterInfo>();

	private final List<String> consumes = new ArrayList<String>();

	private final List<String> produces = new ArrayList<String>();

	public static class Builder {

		private final String serviceName;

		private final String serviceUrl;

		private final String serviceMethod;

		private Type resultType;

		private List<ParameterInfo> pathParams = new ArrayList<ParameterInfo>();

		private List<ParameterInfo> queryParams = new ArrayList<ParameterInfo>();

		private List<ParameterInfo> formParams = new ArrayList<ParameterInfo>();

		private List<String> consumes = new ArrayList<String>();

		private List<String> produces = new ArrayList<String>();

		public Builder(String serviceName, String serviceUrl, String serviceMethod) {
			if (serviceName == null || serviceUrl == null || serviceMethod == null) {
				throw new IllegalArgumentException("ServiceName, ServiceUrl and ServiceMethod cannot be null");
			}
			this.serviceName = serviceName;
			this.serviceUrl = serviceUrl;
			this.serviceMethod = serviceMethod;
		}

		public void addPathParameter(ParameterInfo pathParameter) {
			if (pathParameter == null) {
				throw new IllegalArgumentException("PathParameter cannot be null");
			}
			if (!serviceUrl.contains("{" + pathParameter.getName() + "}")) {
				throw new IllegalStateException(String.format("Path parameter '%s' is not included in service url '%s'",
						pathParameter.getName(), serviceUrl));
			}
			this.pathParams.add(pathParameter);
		}

		public void addQueryParameter(ParameterInfo queryParameter) {
			if (queryParameter == null) {
				throw new IllegalArgumentException("QueryParameter cannot be null");
			}
			this.queryParams.add(queryParameter);
		}

		public void addFormParameter(ParameterInfo formParameter) {
			if (formParameter == null) {
				throw new IllegalArgumentException("FormParameter cannot be null");
			}
			this.formParams.add(formParameter);
		}

		public void addProduces(List<String> produces) {
			if (produces == null) {
				throw new IllegalArgumentException("Produces cannot be null");
			}
			this.produces.addAll(produces);
		}

		public void addConsumes(List<String> consumes) {
			if (consumes == null) {
				throw new IllegalArgumentException("Consumes cannot be null");
			}
			this.consumes.addAll(consumes);
		}

		public void setResultType(Type resultType) {
			this.resultType = resultType;
		}

		public ServiceInfoImpl build() {
			return new ServiceInfoImpl(this);
		}
	}

	private ServiceInfoImpl(Builder builder) {
		this.serviceName = builder.serviceName;
		this.serviceUrl = builder.serviceUrl;
		this.serviceMethod = builder.serviceMethod;
		this.resultType = builder.resultType;
		this.pathParams.addAll(builder.pathParams);
		this.queryParams.addAll(builder.queryParams);
		this.formParams.addAll(builder.formParams);
		this.consumes.addAll(builder.consumes);
		this.produces.addAll(builder.produces);
	}

	public String getServiceName() {
		return serviceName;
	}

	public String getServiceUrl() {
		return serviceUrl;
	}

	public String getServiceMethod() {
		return serviceMethod;
	}

	public Type getResultType() {
		if (isResultTypeVoid()) {
			throw new IllegalStateException("Service result type is void");
		}
		return resultType;
	}

	public List<ParameterInfo> getPathParams() {
		return Collections.unmodifiableList(pathParams);
	}

	public List<ParameterInfo> getQueryParams() {
		return Collections.unmodifiableList(queryParams);
	}

	public List<ParameterInfo> getFormParams() {
		return Collections.unmodifiableList(formParams);
	}

	public List<String> getConsumes() {
		return Collections.unmodifiableList(consumes);
	}

	public List<String> getProduces() {
		return Collections.unmodifiableList(produces);
	}

	public boolean isResultTypeVoid() {
		return resultType == null;
	}

	public boolean containsPathParam(String name) {
		ParameterInfo parameterInfo = getParameterForName(name, pathParams);
		return parameterInfo != null;
	}

	public ParameterInfo getPathParam(String name) {
		ParameterInfo parameterInfo = getParameterForName(name, pathParams);
		if (parameterInfo == null) {
			throw new IllegalStateException(String.format("ServiceInfo '%s' does not contain path param '%s'", serviceName, name));
		}
		return parameterInfo;
	}

	public boolean containsQueryParam(String name) {
		ParameterInfo parameterInfo = getParameterForName(name, queryParams);
		return parameterInfo != null;
	}

	public ParameterInfo getQueryParam(String name) {
		ParameterInfo parameterInfo = getParameterForName(name, queryParams);
		if (parameterInfo == null) {
			throw new IllegalStateException(String.format("ServiceInfo '%s' does not contain query param '%s'", serviceName, name));
		}
		return parameterInfo;
	}

	public boolean containsFormParam(String name) {
		ParameterInfo parameterInfo = getParameterForName(name, formParams);
		return parameterInfo != null;
	}

	public ParameterInfo getFormParam(String name) {
		ParameterInfo parameterInfo = getParameterForName(name, formParams);
		if (parameterInfo == null) {
			throw new IllegalStateException(String.format("ServiceInfo '%s' does not contain form param '%s'", serviceName, name));
		}
		return parameterInfo;
	}

	private ParameterInfo getParameterForName(String name, List<ParameterInfo> parameters) {
		ParameterInfo parameter = null;
		int i = 0;
		while (i < parameters.size() && parameter == null) {
			String parameterName = parameters.get(i).getName();
			if (parameterName.equals(name)) {
				parameter = parameters.get(i);
			} else {
				i++;
			}
		}
		return parameter;
	}
}